//+--------------------------------------------------------------------------------
//| Copyright (c) 2012 - 2013 ### an.vuongngocduy
//| This file is a part of Compact Game Engine (CGE)
//+--------------------------------------------------------------------------------
#ifndef __CGE_MATRIX4_H__
#define __CGE_MATRIX4_H__

#include "cgeCommons.h"
#include "cgeMath.h"
#include "cgeVector3.h"

namespace cge
{
	namespace std
	{
		//! Template class represents 4x4 row-major matrix , often used for 3d transformation.
		template<class TVal>
		class Matrix4
		{
		public:
			//! Default constructor
			Matrix4()
			{
				LoadZero();
			}


			//! Construct matrix from 16 specified values
			Matrix4(TVal m00, TVal m01, TVal m02, TVal m03, 
				   TVal m10, TVal m11, TVal m12, TVal m13, 
				   TVal m20, TVal m21, TVal m22, TVal m23, 
				   TVal m30, TVal m31, TVal m32, TVal m33)
			{
				Set(m00, m01, m02, m03, m10, m11, m12, m13, 
					m20, m21, m22, m23, m30, m31, m32, m33);
			}


			//! Construct matrix from buffer
			Matrix4(const TVal* arr, u32 start = 0, u32 count = 16)
			{
				LoadZero();
				Set(arr, start, count);
			}


			//! Set matrix values from 16 specified values
			Matrix4& Set(TVal m00, TVal m01, TVal m02, TVal m03, 
					TVal m10, TVal m11, TVal m12, TVal m13, 
					TVal m20, TVal m21, TVal m22, TVal m23, 
				    TVal m30, TVal m31, TVal m32, TVal m33)
			{
				M00 = m00; M01 = m01; M02 = m02; M03 = m03;
				M10 = m10; M11 = m11; M12 = m12; M13 = m13;
				M20 = m20; M21 = m21; M22 = m22; M23 = m23;
				M30 = m30; M31 = m31; M32 = m32; M33 = m33;
				return *this;
			}


			//! Set matrix values from buffer
			Matrix4& Set(const TVal* arr, u32 start = 0, u32 count = 16)
			{
				for(u32; i < 16 && i < count; i++)
					A1D[i] = arr[start + i];
				return *this;	
			}


			//! Set translation elements of current matrix
			Matrix4& SetTranslation(const Vector3<TVal>& v3)
			{
				M30 = v3.X;	M31 = v3.Y;	M32 = v3.Z;	
			}

			Matrix4& SetTranslation(TVal x, TVal y, TVal z)
			{
				M30 = x;	M31 = y;	M32 = z;
				return *this;				
			}

			
			//! Get translation elements of current matrix
			Vector3<TVal> GetTranslation() const
			{
				return Vector3<TVal>(M30, M31, M32);
			}
			

			//! Reset matrix to zero matrix
			Matrix4& LoadZero()
			{
				M00 = M01 = M02 = M03 =
				M10 = M11 = M12 = M13 =
				M20 = M21 = M22 = M23 =
				M30 = M31 = M32 = M33 = (TVal)0;
				return *this;
			}


			//! Set matrix to identity matrix
			Matrix4& LoadIdentity()
			{
				M00 = M11 = M22 = M33 = (TVal) 1;
				M01 = M02 = M03 =
				M10 = M12 = M13 =
				M20 = M21 = M23 =
				M30 = M31 = M32 = (TVal) 0;
				return *this;
			}


			//! Build a LHS view matrix
			Matrix4& LookAtLHS(Vector3f eyePos, Vector3f targetPos)
			{
				return *this;
			}


			//! Build a RHS view matrix
			Matrix4& LookAtRHS(Vector3f eyePos, Vector3f targetPos)
			{
				Vector3f zaxis = eyePos - targetPos;
				Vector3f xaxis = zaxis.Cross(Vector3f(0, 1.0f, 0));
				Vector3f yaxis = zaxis.Cross(xaxis);
				zaxis.Normalized();
				yaxis.Normalized();
				zaxis.Normalized();
				Set( xaxis.X,	yaxis.X,	zaxis.X, 0.0,
					 xaxis.Y,	yaxis.Y,	zaxis.Y, 0.0,
					 xaxis.Z,	yaxis.Z,	zaxis.Z, 0.0,
					 -eyePos.X , -eyePos.Y , -eyePos.Z, 1.0f);
				return *this;
			}


			//! Build a LHS perspective projection matrix
			Matrix4& PerspectiveLHS()
			{
				return *this;
			}


			//! Build a RHS perspective projection matrix
			Matrix4& PerspectiveRHS()
			{
				return *this;
			}


			//! Get the determinant of current matrix
			f32 Determinant() const
			{
				return (M00*M11*M22*M33) - (M00*M11*M32*M23) - (M00*M12*M21*M33) + (M00*M12*M31*M23) + (M00*M13*M21*M32) - (M00*M13*M31*M22)
					- (M01*M10*M22*M33) + (M01*M10*M32*M23) + (M01*M12*M20*M33) - (M01*M12*M30*M23) - (M01*M13*M20*M32) + (M01*M13*M30*M22)
					+ (M02*M10*M21*M33) - (M02*M10*M31*M23) - (M02*M11*M20*M33) + (M02*M11*M30*M23) + (M02*M13*M20*M31) - (M02*M13*M30*M21)
					- (M03*M10*M21*M32) + (M03*M10*M31*M22) + (M03*M11*M20*M32) - (M03*M11*M30*M22) - (M03*M12*M20*M31) + (M03*M12*M30*M21);		

			}

			
			//! Get transposed matrix
			Matrix4 Transpose() const
			{
				return Matrix(M00, M10, M20, M30,
							  M01, M11, M21, M31,
							  M02, M12, M22, M32,
							  M03, M13, M23, M33);
			}
			
			
			//! Get the cofactor matrix
			Matrix4 Cofactor() const
			{
				return Matrix4(DET3(M11, M12, M13, M21, M22, M23, M31, M32, M33),
							DET3(M10, M12, M13, M20, M22, M23, M30, M32, M33),
							DET3(M10, M11, M13, M20, M21, M30, M31, M32, M33),
							DET3(M10, M11, M12, M20, M21, M22, M30, M31, M32),

							DET3(M01, M02, M03, M21, M22, M23, M31, M32, M33),
							DET3(M00, M02, M03, M20, M22, M23, M30, M32, M33),
							DET3(M00, M01, M03, M20, M21, M23, M30, M31, M33),
							DET3(M00, M01, M02, M20, M21, M22, M30, M31, M32),

							DET3(M01, M02, M03, M11, M12, M13, M31, M32, M33),
							DET3(M00, M02, M03, M10, M12, M13, M30, M32, M33),
							DET3(M00, M01, M03, M10, M11, M13, M30, M31, M33),
							DET3(M00, M01, M02, M10, M11, M12, M30, M31, M32),

							DET3(M01, M02, M03, M11, M12, M13, M21, M22, M23),
							DET3(M00, M02, M03, M10, M12, M13, M20, M22, M23),
							DET3(M00, M01, M03, M10, M11, M13, M20, M21, M23),
							DET3(M00, M01, M02, M10, M11, M12, M20, M21, M22));
			}


			//! Get adjoint of current matrix 
			Matrix4 Adjoint() const
			{
				return Matrix4(DET3(M11, M12, M13, M21, M22, M23, M31, M32, M33),
							DET3(M01, M02, M03, M21, M22, M23, M31, M32, M33),
							DET3(M01, M02, M03, M11, M12, M13, M31, M32, M33),
							DET3(M01, M02, M03, M11, M12, M13, M21, M22, M23),

							DET3(M10, M12, M13, M20, M22, M23, M30, M32, M33),
							DET3(M00, M02, M03, M20, M22, M23, M30, M32, M33),
							DET3(M00, M02, M03, M10, M12, M13, M30, M32, M33),
							DET3(M00, M02, M03, M10, M12, M13, M20, M22, M23),

							DET3(M10, M11, M13, M20, M21, M30, M31, M32, M33),
							DET3(M00, M01, M03, M20, M21, M23, M30, M31, M33),
							DET3(M00, M01, M03, M10, M11, M13, M30, M31, M33),
							DET3(M00, M01, M03, M10, M11, M13, M20, M21, M23),

							DET3(M10, M11, M12, M20, M21, M22, M30, M31, M32),
							DET3(M00, M01, M02, M20, M21, M22, M30, M31, M32),
							DET3(M00, M01, M02, M10, M11, M12, M30, M31, M32),
							DET3(M00, M01, M02, M10, M11, M12, M20, M21, M22));
			}


			//! Return inverse matrix or identity matrix if matrix is not invertible
			Matrix4 Inverse() const
			{
				f32 det = Determinant();
				if(det == 0.0f)
					return Matrix4().LoadIdentity();
				f32 invDet = 1 / det;
				Matrix4 adjoint = Adjoint();
				return (adjoint *= invDet);
			}



			//! Negate value of matrix.
			Matrix4 operator - (void) const
			{
				return Matrix4
				(
					-M00, -M01, -M02, -M03,
					-M10, -M11, -M12, -M13,
					-M20, -M21, -M22, -M23,
					-M30, -M31, -M32, -M33
				);
			}


			//! Multiply matrix with a scalar number
			template<class TArg>
			Matrix4 operator * (TArg scalar) const
			{
				Matrix4 ret(*this);
				return (ret *= scalar);
			}


			//! Return sum matrix of two 4x4 matrix
			Matrix4 operator * (const Matrix4& m4) const
			{
				Matrix4 ret(*this);
				return (ret *= m4);
			}


			//! Return sum matrix of two 4x4 matrix
			Matrix4 operator + (const Matrix4& m4) const
			{
				Matrix4 ret(*this);
				return (ret += m4);
			}


			//! Return subtraction matrix of two 4x4 matrix
			Matrix4 operator - (const Matrix4& m4) const
			{
				Matrix4 ret(*this);
				return (ret -= m4);
			}


			//! Determines whether two specified 4x4 matrices have the same values.
			b8 operator == (const Matrix4& m4) const
			{
				for(u32 i = 0; i < 16; i++)
					if(A1D[i] != m4.A1D[i])
						return false;
				return true;
			}


			//! Determines whether two specified 4x4 matrices have different values.
			b8 operator != (const Matrix4& m4) const
			{
				return !(*this == m4);
			}


			//! Copy another matrix's values into current matrix
			Matrix4& operator = (const Matrix4& m4)
			{
				if(this != &m4) 
					Set(m4.A1D);
				return *this;
			}


			//! Add current matrix with another matrix
			Matrix4& operator += (const Matrix4& m4)
			{
				Set(M00 + m4.M00, M01 + m4.M01, M02 + m4.M02, M03 + m4.M03,
					M10 + m4.M10, M11 + m4.M11, M12 + m4.M12, M13 + m4.M13,
					M20 + m4.M20, M21 + m4.M21, M22 + m4.M22, M23 + m4.M23,
					M30 + m4.M30, M31 + m4.M31, M32 + m4.M32, M33 + m4.M33);
				return *this;
			}


			//! Subtract current matrix from another matrix
			Matrix4& operator -= (const Matrix4& m4)
			{
				Set(M00 - m4.M00, M01 - m4.M01, M02 - m4.M02, M03 - m4.M03,
					M10 - m4.M10, M11 - m4.M11, M12 - m4.M12, M13 - m4.M13,
					M20 - m4.M20, M21 - m4.M21, M22 - m4.M22, M23 - m4.M23,
					M30 - m4.M30, M31 - m4.M31, M32 - m4.M32, M33 - m4.M33);
				return *this;
			}


			//! Multiply current matrix with another matrix
			Matrix4& operator *= (const Matrix4& m4)
			{
				Set(M00*m4.M00 + M01*m4.M10 + M02*m4.M20 + M03*m4.M30,
					M00*m4.M01 + M01*m4.M11 + M02*m4.M21 + M03*m4.M31,
					M00*m4.M02 + M01*m4.M12 + M02*m4.M22 + M03*m4.M32,
					M00*m4.M03 + M01*m4.M13 + M02*m4.M23 + M03*m4.M33,

					M10*m4.M00 + M11*m4.M10 + M12*m4.M20 + M13*m4.M30,
					M10*m4.M01 + M11*m4.M11 + M12*m4.M21 + M13*m4.M31,
					M10*m4.M02 + M11*m4.M12 + M12*m4.M22 + M13*m4.M32,
					M10*m4.M03 + M11*m4.M13 + M12*m4.M23 + M13*m4.M33,

					M20*m4.M00 + M21*m4.M10 + M22*m4.M20 + M23*m4.M30,
					M20*m4.M01 + M21*m4.M11 + M22*m4.M21 + M23*m4.M31,
					M20*m4.M02 + M21*m4.M12 + M22*m4.M22 + M23*m4.M32,
					M20*m4.M03 + M21*m4.M13 + M22*m4.M23 + M23*m4.M33,

					M30*m4.M00 + M31*m4.M10 + M32*m4.M20 + M33*m4.M30,
					M30*m4.M01 + M31*m4.M11 + M32*m4.M21 + M33*m4.M31,
					M30*m4.M02 + M31*m4.M12 + M32*m4.M22 + M33*m4.M32,
					M30*m4.M03 + M31*m4.M13 + M32*m4.M23 + M33*m4.M33);
				return *this;
			}

			
			//! Multiply matrix with a scalar number
			template<class TArg>
			Matrix4& operator *= (TArg scalar) const
			{
				Set(M00*scalar, M01*scalar, M02*scalar, M03*scalar,
					M10*scalar, M11*scalar, M12*scalar, M13*scalar,
					M20*scalar, M21*scalar, M22*scalar, M23*scalar,
					M30*scalar, M31*scalar, M32*scalar, M33*scalar);
				return *this;
			}


			/*! Matrix is stored as a 1d-array with 16 elements. 
			 *	So every element can be directly accessed using array subscript operator
			 */
			TVal& operator [] (u32 idx)
			{
				return A1D[idx];
			}

			TVal operator [] (u32 idx) const
			{
				return A1D[idx];
			}


			//! Get or set matrix's element at specified position 
			TVal& operator () (u32 row, u32 col)
			{
				return A2D[row][col];
			}

			TVal operator () (u32 idx, u32 col) const
			{
				return A2D[row][col];
			}


		public:
			union
			{
				struct
				{
					TVal M00, M01, M02, M03,
						 M10, M11, M12, M13,
						 M20, M21, M22, M23,
						 M30, M31, M32, M33;
				};
				TVal A2D[4][4]; //as 2-dimensional array
				TVal A1D[16];	//as 1-dimensional array
			};
		};

		//! Floating number 4x4 matrix
		typedef Matrix4<f32> Matrix4f;

		//! Double precision float number 4x4 matrix
		typedef Matrix4<f64> Matrix4d;
	}
}

#endif//__CGE_MATRIX4_H__